#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

#endregion

using Weazel.Gezel.Model;
using Weazel.Gezel.Model.Errors;

namespace Weazel.Gezel.Parser
{
  public static class ParseIpBlock
  {
    public static readonly Set<Token> First = new Set<Token>(
      Token.Ipblock
    );

    public static void Parse(Set<Token> follow, Tokenizer t, Model.Model parent)
    {
      Set<Token> current = new Set<Token>(
        // Token.IpBlock,
        Token.Identifier,
        Token.Colon,
        Token.LeftBrace,
        Token.RightBrace,
        Token.LeftParenthesis,
        Token.RightParenthesis
      );

      current = current.Union(ParseIpBlockDefinition.First);
			
			Position startPosition = t.CurrentPosition;

      t.Accept(Token.Ipblock, follow.Union(current));

      string name = t.CurrentToken == Token.Identifier ?
          t.CurrentString
        : null;

      // create the ip block
      IPBlock ipBlock = new IPBlock(parent, name, startPosition, null);

      current.Remove(Token.Identifier);
      t.Accept(Token.Identifier, follow.Union(current));

      t.Check(follow.Union(follow.Union(current)));

      current.Remove(Token.Colon);
      current.Remove(Token.LeftParenthesis);

      if (t.CurrentToken == Token.LeftParenthesis)
      {
        t.Accept(Token.LeftParenthesis, follow.Union(current));

        List<PortInfo> ports = ParsePortIoList.Parse(follow.Union(current), t);

        foreach (PortInfo port in ports)
        {
          if (port.InPort)
          {
            InPort p = new InPort(
              ipBlock,
              port.Name,
              port.Type.Width,
              port.Type.Signed,
              port.PortPosition,
              port.StartPosition,
							port.EndPosition
            );

            ipBlock.InPorts.Add(port.Name, p);
          }
          else
          {
            OutPort p = new OutPort(
              ipBlock,
              port.Name,
              port.Type.Width,
              port.Type.Signed,
              port.PortPosition,
							port.StartPosition,
							port.EndPosition
            );

            ipBlock.OutPorts.Add(port.Name, p);
          }          
        }

        current.Remove(Token.RightParenthesis);
        t.Accept(Token.RightParenthesis, follow.Union(current));

        current.Remove(Token.LeftBrace);
        t.Accept(Token.LeftBrace, follow.Union(current));

        current.Remove(ParseIpBlockDefinition.First);
        ParseIpBlockDefinition.Parse(follow.Union(current), t, ipBlock);

				ipBlock.EndPosition = t.CurrentPosition;

        current.Remove(Token.RightBrace);
        t.Accept(Token.RightBrace, follow);
      }
      // TODO: How to insert an ipblock copy in the model?
      else if (t.CurrentToken == Token.Colon)
      {
        current.Remove(ParseIpBlockDefinition.First);
        current.Remove(Token.LeftBrace, Token.RightParenthesis, Token.RightBrace, Token.Colon);

        t.Accept(Token.Colon, follow.Union(current));

        /*
        if(t.CurrentToken == Token.Identifier)
          ipBlock.
        */

        t.Accept(Token.Identifier, follow);
      }
      else
      {
        t.Errors.Add(
          new UnexpectedTokenParseError(
            t.CurrentPosition,
            t.CurrentToken,
            follow.Union(current)
          )
        );
      }

      if (name != null)
        parent.IPBlocks.Add(name, ipBlock);
    }
  }

  public static class ParseIpBlockDefinition
  {
    public static readonly Set<Token> First =
      ParseIpBlockDefinitionElement.First;

    public static void Parse(Set<Token> follow, Tokenizer t, IPBlock ipBlock)
    {      
      Set<Token> current = First;

      bool done = false;

      do
      {
        t.Check(follow.Union(current));

        if (ParseIpBlockDefinitionElement.First.Contains(t.CurrentToken))
          ParseIpBlockDefinitionElement.Parse(follow.Union(current), t, ipBlock);
        else
          done = true;

      } while (!done);
    }
  }

  public static class ParseIpBlockDefinitionElement
  {
    public static readonly Set<Token> First =
      ParseIpParam.First.Union(ParseIpType.First);

    public static void Parse(Set<Token> follow, Tokenizer t, IPBlock ipBlock)
    {
      Set<Token> current = First;

      t.Check(follow.Union(First));

      if (ParseIpParam.First.Contains(t.CurrentToken))
      {
        ParseIpParam.Parse(follow.Union(current), t, ipBlock);
      }
      else if (ParseIpType.First.Contains(t.CurrentToken))
      {
        ParseIpType.Parse(follow.Union(current), t, ipBlock);
      }
      else
      {
        t.Errors.Add(new DuplicateIpTypeParseError(ipBlock));
      }
    }
  }

  public static class ParseIpType
  {
    public static readonly Set<Token> First = new Set<Token>(
      Token.Iptype
    );

    public static void Parse(Set<Token> follow, Tokenizer t, IPBlock ipBlock)
    {
      Set<Token> current = new Set<Token>(
        Token.Str,
        Token.Semicolon
      );

      t.Accept(Token.Iptype, follow.Union(current));

      if (t.CurrentToken == Token.Str)
      {
        // check if the type of this block has already been set
        if(ipBlock.IpType != null)          
        {
          t.Errors.Add(
            new UnexpectedTokenParseError(
              t.CurrentPosition,
              t.CurrentToken,
              follow.Union(current)
            )
          );
        }
        
        ipBlock.IpType = t.CurrentString;
      }

      current.Remove(Token.Str);
      t.Accept(Token.Str, follow.Union(current));
      t.Accept(Token.Semicolon, follow);      
    }
  }

  public static class ParseIpParam
  {
    public static readonly Set<Token> First = new Set<Token>(
      Token.Ipparm
    );

    public static void Parse(Set<Token> follow, Tokenizer t, IPBlock ipBlock)
    {
      Set<Token> current = new Set<Token>(
        Token.Str,
        Token.Semicolon
      );

      t.Accept(Token.Ipparm, follow.Union(current));

      if (t.CurrentToken == Token.Str)
      {        
        ipBlock.IpParameters.Add(t.CurrentString);
      }

      current.Remove(Token.Str);
      t.Accept(Token.Str, follow.Union(current));
      t.Accept(Token.Semicolon, follow);
    }
  }
}